#include "netclient.h"

#include <QNetworkReply>
#include <QUuid>

#include "../model/data.h"
#include "../model/datacenter.h"

using namespace model;

namespace network {

NetClient::NetClient(model::DataCenter* dataCenter)
    : dataCenter(dataCenter)
{
    // 不应该在这个环节初始化 websocket 放到 mainWidget 初始化的时候
    // initWebsocket();
}

void NetClient::ping()
{
    QNetworkRequest httpReq;
    httpReq.setUrl(QUrl(HTTP_URL + "/ping"));

    QNetworkReply* httpResp = httpClient.get(httpReq);
    // 不知道响应什么时候收到，所以使用信号槽
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        // 这里面，说明响应已经回来了
        if (httpResp->error() != QNetworkReply::NoError) {
            // 请求失败!
            LOG() << "HTTP 请求失败! " <<httpResp->errorString();
            httpResp->deleteLater();
            return ;
        }
        // 获取到响应的 body
        QByteArray body = httpResp->readAll();
        LOG() << "响应内容: " << body;
        httpResp->deleteLater();
    });
}

///////////////////////////////////////////////////////////////////////////
/// 以下逻辑都是 WebSocket 相关的操作
///////////////////////////////////////////////////////////////////////////

void NetClient::initWebsocket()
{
    // 1. 准备好所有需要的信号槽
    connect(&webSocketClient, &QWebSocket::connected, this, [=]() {
        LOG() << "websocket 连接成功!";
        // 不要忘记! 在 websocket 连接成功之后，发送身份认证消息!
        sendAuth();
    });

    connect(&webSocketClient, &QWebSocket::disconnected, this, [=]() {
        LOG() << "websocket 连接断开!";
    });

    connect(&webSocketClient, &QWebSocket::errorOccurred, this, [=](QAbstractSocket::SocketError error) {
        LOG() << "websocket 连接出错!" << error;
    });

    connect(&webSocketClient, &QWebSocket::textMessageReceived, this, [=](const QString& message) {
        LOG() << "websocket 收到文本消息!" << message;
    });

    connect(&webSocketClient, &QWebSocket::binaryMessageReceived, this, [=](const QByteArray& byteArray) {
        LOG() << "websocket 收到二进制消息!" << byteArray.length();
        bite_im::NotifyMessage notifyMessage;
        notifyMessage.deserialize(&serializer, byteArray);
        handleWsResponse(notifyMessage);
    });

    // 2. 和服务器真正建立连接
    webSocketClient.open(WEBSOCKET_URL);
}

// 处理所有的 websocket 推送数据
void NetClient::handleWsResponse(const bite_im::NotifyMessage &notifyMessage)
{
    if (notifyMessage.notifyType() == bite_im::NotifyTypeGadget::NotifyType::CHAT_MESSAGE_NOTIFY) {
        // 收到消息
        // 1. 把 pb 中的 MessageInfo 转成客户端自己的 Message
        Message message;
        message.load(notifyMessage.newMessageInfo().messageInfo());
        // 2. 针对自己的 message 做进一步的处理
        handleWsMessage(message);
    } else if (notifyMessage.notifyType() == bite_im::NotifyTypeGadget::NotifyType::CHAT_SESSION_CREATE_NOTIFY) {
        // 创建新的会话通知
        ChatSessionInfo chatSessionInfo;
        chatSessionInfo.load(notifyMessage.newChatSessionInfo().chatSessionInfo());
        handleWsSessionCreate(chatSessionInfo);
    } else if (notifyMessage.notifyType() == bite_im::NotifyTypeGadget::NotifyType::FRIEND_ADD_APPLY_NOTIFY) {
        // 添加好友申请通知
        UserInfo userInfo;
        userInfo.load(notifyMessage.friendAddApply().userInfo());
        handleWsAddFriendApply(userInfo);
    } else if (notifyMessage.notifyType() == bite_im::NotifyTypeGadget::NotifyType::FRIEND_ADD_PROCESS_NOTIFY) {
        // 添加好友申请的处理结果通知 (你添加别人)
        UserInfo userInfo;
        userInfo.load(notifyMessage.friendProcessResult().userInfo());
        bool agree = notifyMessage.friendProcessResult().agree();
        handleWsFriendProcess(userInfo, agree);
    } else if (notifyMessage.notifyType() == bite_im::NotifyTypeGadget::NotifyType::FRIEND_REMOVE_NOTIFY) {
        // 删除好友通知
        const QString& userId = notifyMessage.friendRemove().userId();
        handleWsRemoveFriend(userId);
    } else {

    }
}

void NetClient::handleWsMessage(const model::Message &message)
{
    QList<Message>* messageList = dataCenter->getRecentMessageList(message.chatSessionId);
    if (messageList == nullptr) {
        // 1. 如果当前这个消息所属的会话，里面的消息列表，没有在本地加载，此时就需要通过网络先加载整个消息列表
        connect(dataCenter, &DataCenter::getRecentMessageListDoneNoUI, this, &NetClient::receiveMessage, Qt::UniqueConnection);
        dataCenter->getRecentMessageListAsync(message.chatSessionId, false);
    } else {
        // 2. 如果当前这个消息所属的会话，里面的消息已经在本地加载了，直接把这个消息尾插到消息列表中即可
        messageList->push_back(message);
        this->receiveMessage(message.chatSessionId);
    }
}

void NetClient::handleWsRemoveFriend(const QString &userId)
{
    // 1. 删除数据 DataCenter 好友列表的数据
    dataCenter->removeFriend(userId);
    // 2. 通知界面变化
    emit dataCenter->deleteFriendDone();
}

void NetClient::handleWsAddFriendApply(const model::UserInfo &userInfo)
{
    // 1. DataCenter 中有一个好友申请列表，需要把这个数据添加到好友申请列表
    QList<UserInfo>* applyList = dataCenter->getApplyList();
    if (applyList == nullptr) {
        LOG() << "客户端没有加载到好友申请列表!";
        return ;
    }
    // 把新的元素放到列表前面
    applyList->push_front(userInfo);
    // 2. 通知界面更新
    emit dataCenter->receiveFriendApplyDone();
}

void NetClient::handleWsFriendProcess(const model::UserInfo &userInfo, bool agree)
{
    if (agree) {
        // 对方同意了你的好友申请
        QList<UserInfo>* friendList = dataCenter->getFriendList();
        if (friendList == nullptr) return ;
        friendList->push_front(userInfo);
    }
    // 同时也更新一下界面
    emit dataCenter->receiveFriendProcessDone(userInfo.nickname, agree);
}

void NetClient::handleWsSessionCreate(const model::ChatSessionInfo &chatSessionInfo)
{
    // 把这个 chatSessionInfo 添加到会话列表中即可
    QList<ChatSessionInfo>* chatSessionList = dataCenter->getChatSessionList();
    if (chatSessionList == nullptr) {
        LOG() << "客户端没有加载会话列表";
        return ;
    }
    // 添加到列表头部
    chatSessionList->push_front(chatSessionInfo);
    // 发送一个信号，通知界面更新
    emit dataCenter->receiveSessionCreateDone();
}

///////////////////////////////////////////////////////////////////////////
/// 以下逻辑都是 HTTP 相关的操作
///////////////////////////////////////////////////////////////////////////

void NetClient::sendAuth()
{
    bite_im::ClientAuthenticationReq req;
    req.setRequestId(makeRequestId());
    req.setSessionId(dataCenter->getLoginSessionId());
    // 使用序列化器进行序列化
    QByteArray body = req.serialize(&serializer);
    webSocketClient.sendBinaryMessage(body);
    LOG() << "[WS身份认证] requestId=" << req.requestId() << ", loginSessionId=" << req.sessionId();
}

QString NetClient::makeRequestId()
{
    // 基本要求，确保每个请求的 id 都是不重复的 (唯一的) UUID
    return "R" + QUuid::createUuid().toString().sliced(25, 12);
}

// 通过这个函数，把发送 HTTP 请求操作封装一下
QNetworkReply *NetClient::sendHttpRequest(const QString &apiPath, const QByteArray &body)
{
    QNetworkRequest httpReq;
    httpReq.setUrl(QUrl(HTTP_URL + apiPath));
    httpReq.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-protobuf");

    QNetworkReply* httpResp = httpClient.post(httpReq, body);
    return httpResp;
}

// 在这个函数内部，完成具体的网络通信即可
void NetClient::getMyself(const QString &loginSessionId)
{
    // 1. 构造出 HTTP 请求 body 部分
    bite_im::GetUserInfoReq req;
    req.setRequestId(makeRequestId());
    req.setSessionId(loginSessionId);
    QByteArray body = req.serialize(&serializer);
    LOG() << "[获取个人信息] 发送请求 requestId=" << req.requestId() << ", loginSessionId=" << loginSessionId;

    // 2. 构造出 HTTP 请求并发送
    QNetworkReply* httpResp = sendHttpRequest("/service/user/get_user_info", body);

    // 3. 通过信号槽，获取到当前的响应  finished 信号表示响应已经返回到客户端了
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        // a) 先处理响应对象
        bool ok = false;
        QString reason;
        auto resp = handleHttpResponse<bite_im::GetUserInfoRsp>(httpResp, &ok, &reason);

        // b) 判定响应是否正确
        if (!ok) {
            LOG() << "[获取个人信息] 出错! requestId=" << req.requestId() << "reason=" << reason;
            return ;
        }

        // c) 把响应的数据，保存到 DataCenter 中
        dataCenter->resetMyself(resp);

        // d) 通知调用逻辑，响应已经处理完了,仍然通过信号槽，通知
        emit dataCenter->getMyselfDone();

        // e) 打印日志
        LOG() << "[获取个人信息] 响应完成 requestId=" << req.requestId();
    });
}

void NetClient::getFriendList(const QString &loginSessionId)
{
    // 1. 通过 protobuf 构造 body
    bite_im::GetFriendListReq req;
    req.setRequestId(makeRequestId());
    req.setSessionId(loginSessionId);
    QByteArray body = req.serialize(&serializer);
    LOG() << "[获取好友列表] 发送请求 requestId=" << req.requestId() << ", loginSessionId=" << loginSessionId;

    // 2. 发送 HTTP 请求
    QNetworkReply* httpResp = this->sendHttpRequest("/service/friend/get_friend_list", body);

    // 3. 处理响应
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        // a) 解析响应
        bool ok = false;
        QString reason;
        auto friendListResp = this->handleHttpResponse<bite_im::GetFriendListRsp>(httpResp, &ok, &reason);

        // b) 判定响应是否正确
        if (!ok) {
            LOG() << "[获取好友列表] 失败! requestId=" << req.requestId() << ", reason=" << reason;
            return ;
        }

        // c) 把结果保存在 DataCenter 中
        dataCenter->resetFriendList(friendListResp);

        // d) 发送信号来通知界面，当前这个操作完成了
        emit dataCenter->getFriendListDone();

        // e) 打印日志
        LOG() << "[获取好友列表] 响应完成 requestId=" << req.requestId();
    });
}

void NetClient::getChatSessionList(const QString &loginSessionId)
{
    // 1. 通过 protobuf 构造 body
    bite_im::GetChatSessionListReq req;
    req.setRequestId(makeRequestId());
    req.setSessionId(loginSessionId);
    QByteArray body = req.serialize(&serializer);
    LOG() << "[获取会话列表] 发送请求 requestId=" << req.requestId() << ", loginSessionId=" << loginSessionId;

    // 2. 发送 HTTP 请求
    QNetworkReply* httpResp = this->sendHttpRequest("/service/friend/get_chat_session_list", body);

    // 3. 处理响应
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        // a) 解析响应
        bool ok = false;
        QString reason;
        auto sessionListResp = this->handleHttpResponse<bite_im::GetChatSessionListRsp>(httpResp, &ok, &reason);

        // b) 判定响应是否正确
        if (!ok) {
            LOG() << "[获取会话列表] 失败! requestId=" << req.requestId() << ", reason=" << reason;
            return ;
        }

        // c) 把结果保存在 DataCenter 中
        dataCenter->resetChatSessionList(sessionListResp);

        // d) 发送信号来通知界面，当前这个操作完成了
        emit dataCenter->getChatSessionListDone();

        // e) 打印日志
        LOG() << "[获取会话列表] 响应完成 requestId=" << req.requestId();
    });
}

void NetClient::getApplyList(const QString &loginSessionId)
{
    // 1. 通过 protobuf 构造 body
    bite_im::GetPendingFriendEventListReq req;
    req.setRequestId(makeRequestId());
    req.setSessionId(loginSessionId);
    QByteArray body = req.serialize(&serializer);
    LOG() << "[获取好友申请列表] 发送请求 requestId=" << req.requestId() << ", loginSessionId=" << loginSessionId;

    // 2. 发送 HTTP 请求
    QNetworkReply* httpResp = this->sendHttpRequest("/service/friend/get_pending_friend_events", body);

    // 3. 处理响应
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        // a) 解析响应
        bool ok = false;
        QString reason;
        auto pbResp = this->handleHttpResponse<bite_im::GetPendingFriendEventListRsp>(httpResp, &ok, &reason);

        // b) 判定响应是否正确
        if (!ok) {
            LOG() << "[获取好友申请列表] 失败! requestId=" << req.requestId() << ", reason=" << reason;
            return ;
        }

        // c) 把结果保存在 DataCenter 中
        dataCenter->resetApplyList(pbResp);

        // d) 发送信号来通知界面，当前这个操作完成了
        emit dataCenter->getApplyListDone();

        // e) 打印日志
        LOG() << "[获取好友申请列表] 响应完成 requestId=" << req.requestId();
    });
}

void NetClient::getRecentMessageList(const QString &loginSessionId, const QString &chatSessionId, bool updateUI)
{
    // 1. 通过 protobuf 构造 body
    bite_im::GetRecentMsgReq req;
    req.setRequestId(makeRequestId());
    req.setChatSessionId(chatSessionId);
    req.setMsgCount(50);    // 此处固定获取最近的 50 条
    req.setSessionId(loginSessionId);
    QByteArray body = req.serialize(&serializer);
    LOG() << "[获取最近消息] 发送请求 requestId=" << req.requestId() << ", loginSessionId=" << loginSessionId << ", chatSessionId=" << chatSessionId;

    // 2. 发送 HTTP 请求
    QNetworkReply* httpResp = this->sendHttpRequest("/service/message_storage/get_recent", body);

    // 3. 处理响应
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        // a) 解析响应，反序列化
        bool ok = false;
        QString reason;
        auto pbRsp = this->handleHttpResponse<bite_im::GetRecentMsgRsp>(httpResp, &ok, &reason);

        // b) 判定响应是否正确
        if (!ok) {
            LOG() << "[获取最近消息] 失败! requestId=" << req.requestId() << ", reason=" << reason;
            return ;
        }

        // c) 把结果保存在 DataCenter 中
        dataCenter->resetRecentMessageList(chatSessionId, pbRsp);

        // d) 发送信号
        if (updateUI) {
            emit dataCenter->getRecentMessageListDone(chatSessionId);
        } else {
            emit dataCenter->getRecentMessageListDoneNoUI(chatSessionId);
        }

        // e) 打印日志
        LOG() << "[获取最近消息] 响应完成 requestId=" << req.requestId();
    });
}

// extraInfo 传递扩展信息，表示文件名之类的
void NetClient::sendMessage(const QString &loginSessionId, const QString &chatSessionId, model::MessageType messageType, const QByteArray &content, const QString& extraInfo)
{
    // 1. 通过 protobuf 构造 body
    bite_im::NewMessageReq req;
    req.setRequestId(makeRequestId());
    req.setSessionId(loginSessionId);
    req.setChatSessionId(chatSessionId);

    // 构造 MesssageContent
    bite_im::MessageContent messageContent;
    if (messageType == MessageType::TEXT_TYPE) {
        messageContent.setMessageType(bite_im::MessageTypeGadget::MessageType::STRING);

        bite_im::StringMessageInfo stringMessageInfo;
        stringMessageInfo.setContent(content);
        messageContent.setStringMessage(stringMessageInfo);
    } else if (messageType == MessageType::IMAGE_TYPE) {
        messageContent.setMessageType(bite_im::MessageTypeGadget::MessageType::IMAGE);

        bite_im::ImageMessageInfo imageMessageInfo;
        imageMessageInfo.setFileId("");            // fileId 是文件在服务器存储的时候，生成的 id，此时还无法获取到
        imageMessageInfo.setImageContent(content);
        messageContent.setImageMessage(imageMessageInfo);
    } else if (messageType == MessageType::FILE_TYPE) {
        messageContent.setMessageType(bite_im::MessageTypeGadget::MessageType::FILE);

        bite_im::FileMessageInfo fileMessageInfo;
        fileMessageInfo.setFileId("");
        fileMessageInfo.setFileSize(content.size());
        fileMessageInfo.setFileName(extraInfo);
        fileMessageInfo.setFileContents(content);
        messageContent.setFileMessage(fileMessageInfo);
    } else if (messageType == MessageType::SPEECH_TYPE) {
        messageContent.setMessageType(bite_im::MessageTypeGadget::MessageType::SPEECH);

        bite_im::SpeechMessageInfo speechMessageInfo;
        speechMessageInfo.setFileId("");
        speechMessageInfo.setFileContents(content);
        messageContent.setSpeechMessage(speechMessageInfo);
    } else {
        LOG() << "错误的消息类型, messageType=" << messageType;
        return ;
    }

    req.setMessage(messageContent);

    // 序列化
    QByteArray body = req.serialize(&serializer);
    LOG() << "[发送消息] 发送请求 requestId=" << req.requestId() << ", loginSessionId=" << loginSessionId << ", chatSessionId=" << chatSessionId << "messageType=" << req.message().messageType();

    // 发送 HTTP 请求
    QNetworkReply* httpResp = this->sendHttpRequest("/service/message_transmit/new_message", body);

    // 处理响应
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        // a) 解析响应，反序列化
        bool ok = false;
        QString reason;
        auto pbRsp = this->handleHttpResponse<bite_im::NewMessageRsp>(httpResp, &ok, &reason);

        // b) 判定响应是否正确
        if (!ok) {
            LOG() << "[发送消息] 失败! requestId=" << req.requestId() << ", reason=" << reason;
            return ;
        }

        // c) 此处只是需要记录 "成功失败"，不需要把内容写入到 DataCenter 中

        // d) 发送信号
        emit dataCenter->sendMessageDone(messageType, content, extraInfo);

        // e) 打印日志
        LOG() << "[发送消息] 响应完成 requestId=" << req.requestId();
    });
}

void NetClient::receiveMessage(const QString &chatSessionId)
{
    // 先需要判定一下，当前这个收到的消息对应的会话，是否是正在被用户选中的 "当前会话"
    // 当前会话，就需要把消息，显示到消息展示区，也需要更新会话列表的消息预览
    // 不是当前会话，只需要更新回话列表中的消息预览，并且更新 "未读消息数目"
    if (chatSessionId == dataCenter->getCurrentChatSessionId()) {
        // 收到的消息，是选中的会话
        // 在消息展示区，新增一个消息
        const Message& lastMessage = dataCenter->getRecentMessageList(chatSessionId)->back();
        // 通过信号，让 NetClient 模块，能够通知界面 （消息展示区)
        emit dataCenter->receiveMessageDone(lastMessage);
    } else {
        // 收到的消息，不是选中的会话
        // 更新未读消息数目
        dataCenter->addUnread(chatSessionId);
    }
    // 统一更新会话列表的消息预览
    emit dataCenter->updateLastMessage(chatSessionId);
}

void NetClient::changeNickname(const QString &loginSessionId, const QString &nickname)
{
    // 1. 通过 protobuf 构造 body
    bite_im::SetUserNicknameReq pbReq;
    pbReq.setRequestId(makeRequestId());
    pbReq.setSessionId(loginSessionId);
    pbReq.setNickname(nickname);

    // 序列化
    QByteArray body = pbReq.serialize(&serializer);
    LOG() << "[更改昵称] 发送请求 requestId=" << pbReq.requestId() << ", loginSessionId=" << loginSessionId << ", nickname=" << nickname;

    // 发送 HTTP 请求
    QNetworkReply* httpResp = this->sendHttpRequest("/service/user/set_nickname", body);

    // 处理响应
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        // a) 解析响应，反序列化
        bool ok = false;
        QString reason;
        auto pbRsp = this->handleHttpResponse<bite_im::SetUserNicknameRsp>(httpResp, &ok, &reason);

        // b) 判定响应是否正确
        if (!ok) {
            LOG() << "[更改昵称] 失败! requestId=" << pbReq.requestId() << ", reason=" << reason;
            return ;
        }

        // c) 把结果保存在 DataCenter 中
        dataCenter->resetNickname(nickname);

        // d) 发送信号
        emit dataCenter->changeNicknameDone();

        // 修改数据中心对应的消息内容
        QHash<QString, QList<Message>>* recentMessages = DataCenter::getInstance()->getRecentMessages();
        for (auto it = recentMessages->begin(); it != recentMessages->end(); ++it) {
            QList<Message>& messageList = it.value();

            // 遍历每个联系人的消息列表
            for (Message& message : messageList) {
                if (message.sender.userId == DataCenter::getInstance()->getMyself()->userId) {
                    message.sender.nickname = nickname;
                }
            }
        }

        // e) 打印日志
        LOG() << "[更改昵称] 响应完成 requestId=" << pbReq.requestId();
    });
}

void NetClient::changeSignature(const QString &loginSessionId, const QString &sign)
{
    // 1. 通过 protobuf 构造 body 并序列化
    bite_im::SetUserDescriptionReq pbReq;
    pbReq.setRequestId(makeRequestId());
    pbReq.setSessionId(loginSessionId);
    pbReq.setDescription(sign);
    QByteArray body = pbReq.serialize(&serializer);
    LOG() << "[更改签名] 发送请求 requestId=" << pbReq.requestId() << ", loginSessionId=" << loginSessionId << ", signature=" << sign;

    // 2. 发送 HTTP 请求
    QNetworkReply* httpResp = this->sendHttpRequest("/service/user/set_description", body);

    // 3. 处理响应
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        // a) 解析响应，反序列化
        bool ok = false;
        QString reason;
        auto pbResp = this->handleHttpResponse<bite_im::SetUserDescriptionRsp>(httpResp, &ok, &reason);

        // b) 判定响应是否正确
                      if (!ok) {
            LOG() << "[更改签名] 失败! requestId=" << pbReq.requestId() << ", reason=" << reason;
            return ;
        }

        // c) 把结果保存在 DataCenter 中
        dataCenter->resetSignature(sign);

        // d) 发送信号
        emit dataCenter->changeSignatureDone();

        // e) 打印日志
        LOG() << "[更改签名] 响应完成 requestId=" << pbReq.requestId();
    });
}

void NetClient::getVerifyCode(const QString &phone)
{
    // 1. 通过 protobuf 构造 body 并序列化
    bite_im::PhoneVerifyCodeReq pbReq;
    pbReq.setRequestId(makeRequestId());
    pbReq.setPhoneNumber(phone);
    QByteArray body = pbReq.serialize(&serializer);
    LOG() << "[获取手机验证码] 发送请求 requestId=" << pbReq.requestId() << ", phone=" << phone;

    // 2. 发送 HTTP 请求
    QNetworkReply* httpResp = this->sendHttpRequest("/service/user/get_phone_verify_code", body);

    // 3. 处理响应
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        // a) 解析响应，反序列化
        bool ok = false;
        QString reason;
        auto pbResp = this->handleHttpResponse<bite_im::PhoneVerifyCodeRsp>(httpResp, &ok, &reason);

        // b) 判定响应是否正确
        if (!ok) {
            LOG() << "[获取手机验证码] 失败! requestId=" << pbReq.requestId() << ", reason=" << reason;
            return ;
        }

        // c) 把结果保存在 DataCenter 中
        dataCenter->resetVerifyCodeId(pbResp->verifyCodeId());

        // d) 发送信号
        emit dataCenter->getVerifyCodeIdDone();

        // e) 打印日志
        LOG() << "[获取手机验证码] 响应完成 requestId=" << pbReq.requestId();
    });
}

void NetClient::changePhone(const QString &loginSessionId, const QString &phone, const QString &verifyCodeId, const QString &verifyCode)
{
    // 1. 通过 protobuf 构造 body 并序列化
    bite_im::SetUserPhoneNumberReq pbReq;
    pbReq.setRequestId(makeRequestId());
    pbReq.setSessionId(loginSessionId);
    pbReq.setPhoneNumber(phone);
    pbReq.setPhoneVerifyCodeId(verifyCodeId);
    pbReq.setPhoneVerifyCode(verifyCode);
    QByteArray body = pbReq.serialize(&serializer);
    LOG() << "[修改手机号码] 发送请求 requestId=" << pbReq.requestId() << ", loginSessionId=" << loginSessionId <<", phone="
          << phone << ", verifyCodeId=" << verifyCodeId << ", verifyCode=" << verifyCode;

    // 2. 发送 HTTP 请求
    QNetworkReply* httpResp = this->sendHttpRequest("/service/user/set_phone", body);

    // 3. 处理响应
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        // a) 解析响应，反序列化
        bool ok = false;
        QString reason;
        auto pbResp = this->handleHttpResponse<bite_im::SetUserPhoneNumberRsp>(httpResp, &ok, &reason);

        // b) 判定响应是否正确
        if (!ok) {
            LOG() << "[修改手机号码] 失败! requestId=" << pbReq.requestId() << ", reason=" << reason;
            return ;
        }

        // c) 把结果保存在 DataCenter 中
        dataCenter->resetPhone(phone);

        // d) 发送信号
        emit dataCenter->changePhoneDone();

        // 修改数据中心对应的消息内容
        QHash<QString, QList<Message>>* recentMessages = DataCenter::getInstance()->getRecentMessages();
        for (auto it = recentMessages->begin(); it != recentMessages->end(); ++it) {
            QList<Message>& messageList = it.value();

            // 遍历每个联系人的消息列表
            for (Message& message : messageList) {
                if (message.sender.userId == DataCenter::getInstance()->getMyself()->userId) {
                    message.sender.phone = phone;
                }
            }
        }


        // e) 打印日志
        LOG() << "[修改手机号码] 响应完成 requestId=" << pbReq.requestId();
    });
}

void NetClient::changeAvatar(const QString &loginSessionId, const QByteArray &avatar)
{
    // 1. 通过 protobuf 构造 body 并序列化
    bite_im::SetUserAvatarReq pbReq;
    pbReq.setRequestId(makeRequestId());
    pbReq.setSessionId(loginSessionId);
    pbReq.setAvatar(avatar);
    QByteArray body = pbReq.serialize(&serializer);
    LOG() << "[修改头像] 发送请求 requestId=" << pbReq.requestId() << ", loginSessionId=" << loginSessionId;

    // 2. 发送 HTTP 请求
    QNetworkReply* httpResp = this->sendHttpRequest("/service/user/set_avatar", body);

    // 3. 处理响应
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        // a) 解析响应，反序列化
        bool ok = false;
        QString reason;
        auto pbResp = this->handleHttpResponse<bite_im::SetUserAvatarRsp>(httpResp, &ok, &reason);

        // b) 判定响应是否正确
        if (!ok) {
            LOG() << "[修改头像] 失败! requestId=" << pbReq.requestId() << ", reason=" << reason;
            return ;
        }

        // c) 把结果保存在 DataCenter 中
        dataCenter->resetAvatar(avatar);

        // d) 发送信号
        emit dataCenter->changeAvatarDone();

        // 修改数据中心对应的消息内容
        QHash<QString, QList<Message>>* recentMessages = DataCenter::getInstance()->getRecentMessages();
        for (auto it = recentMessages->begin(); it != recentMessages->end(); ++it) {
            QList<Message>& messageList = it.value();

            // 遍历每个联系人的消息列表
            for (Message& message : messageList) {
                if (message.sender.userId == DataCenter::getInstance()->getMyself()->userId) {
                    message.sender.avatar = makeIcon(avatar);
                }
            }
        }

        // e) 打印日志
        LOG() << "[修改头像] 响应完成 requestId=" << pbReq.requestId();
    });
}

void NetClient::deleteFriend(const QString &loginSessionId, const QString &userId)
{
    // 1. 通过 protobuf 构造 body 并序列化
    bite_im::FriendRemoveReq pbReq;
    pbReq.setRequestId(makeRequestId());
    pbReq.setSessionId(loginSessionId);
    pbReq.setPeerId(userId);
    QByteArray body = pbReq.serialize(&serializer);
    LOG() << "[删除好友] 发送请求 requestId=" << pbReq.requestId() << ", loginSessionId=" << loginSessionId;

    // 2. 发送 HTTP 请求
    QNetworkReply* httpResp = this->sendHttpRequest("/service/friend/remove_friend", body);

    // 3. 处理响应
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        // a) 解析响应，反序列化
        bool ok = false;
        QString reason;
        auto pbResp = this->handleHttpResponse<bite_im::FriendRemoveRsp>(httpResp, &ok, &reason);

        // b) 判定响应是否正确
        if (!ok) {
            LOG() << "[删除好友] 失败! requestId=" << pbReq.requestId() << ", reason=" << reason;
            return ;
        }

        // c) 在 DataCenter 中删除好友
        dataCenter->removeFriend(userId);

        // d) 发送信号，通知调用者当前好友删除完毕
        emit dataCenter->deleteFriendDone();

        // e) 打印日志
        LOG() << "[删除好友] 响应完成 requestId=" << pbReq.requestId();
    });
}

void NetClient::addFriendApply(const QString &loginSessionId, const QString &userId)
{
    // 1. 通过 protobuf 构造 body 并序列化
    bite_im::FriendAddReq pbReq;
    pbReq.setRequestId(makeRequestId());
    pbReq.setSessionId(loginSessionId);
    pbReq.setRespondentId(userId);
    QByteArray body = pbReq.serialize(&serializer);
    LOG() << "[添加好友申请] 发送请求 requestId=" << pbReq.requestId() << ", loginSessionId=" << loginSessionId << ", userId=" << userId;

    // 2. 发送 HTTP 请求
    QNetworkReply* httpResp = this->sendHttpRequest("/service/friend/add_friend_apply", body);

    // 3. 处理响应
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        // a) 解析响应，反序列化
        bool ok = false;
        QString reason;
        auto pbResp = this->handleHttpResponse<bite_im::FriendAddRsp>(httpResp, &ok, &reason);

        // b) 判定响应是否正确
        if (!ok) {
            LOG() << "[添加好友申请] 失败! requestId=" << pbReq.requestId() << ", reason=" << reason;
            return ;
        }

        // c) 此处不记录任何数据

        // d) 发送信号，通知调用者
        emit dataCenter->addFriendApplyDone();

        // e) 打印日志
        LOG() << "[添加好友申请] 响应完成 requestId=" << pbReq.requestId();
    });
}

void NetClient::acceptFriendApply(const QString &loginSessionId, const QString &userId)
{
    // 1. 通过 protobuf 构造 body 并序列化
    bite_im::FriendAddProcessReq pbReq;
    pbReq.setRequestId(makeRequestId());
    pbReq.setSessionId(loginSessionId);
    pbReq.setAgree(true);
    pbReq.setApplyUserId(userId);
    QByteArray body = pbReq.serialize(&serializer);
    LOG() << "[同意好友申请] 发送请求 requestId=" << pbReq.requestId() << ", loginSessionId=" << loginSessionId << ", userId=" << userId;

    // 2. 发送 HTTP 请求
    QNetworkReply* httpResp = this->sendHttpRequest("/service/friend/add_friend_process", body);

    // 3. 处理响应
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        // a) 解析响应，反序列化
        bool ok = false;
        QString reason;
        auto pbResp = this->handleHttpResponse<bite_im::FriendAddProcessRsp>(httpResp, &ok, &reason);

        // b) 判定响应是否正确
        if (!ok) {
            LOG() << "[同意好友申请] 失败! requestId=" << pbReq.requestId() << ", reason=" << reason;
            return ;
        }

        // c) 在 DataCenter 中更新好友列表 从好友申请列表中删除掉
        UserInfo applyUser = dataCenter->removeFromApplyList(userId);
        QList<UserInfo>* friendList = dataCenter->getFriendList();
        friendList->push_front(applyUser);

        // d) 发送信号，通知调用者
        emit dataCenter->acceptFriendApplyDone();

        // e) 打印日志
        LOG() << "[同意好友申请] 响应完成 requestId=" << pbReq.requestId();
    });
}

void NetClient::rejectFriendApply(const QString &loginSessionId, const QString &userId)
{
    // 1. 通过 protobuf 构造 body 并序列化
    bite_im::FriendAddProcessReq pbReq;
    pbReq.setRequestId(makeRequestId());
    pbReq.setSessionId(loginSessionId);
    pbReq.setAgree(false);
    pbReq.setApplyUserId(userId);
    QByteArray body = pbReq.serialize(&serializer);
    LOG() << "[拒绝好友申请] 发送请求 requestId=" << pbReq.requestId() << ", loginSessionId=" << loginSessionId << ", userId=" << userId;

    // 2. 发送 HTTP 请求
    QNetworkReply* httpResp = this->sendHttpRequest("/service/friend/add_friend_process", body);

    // 3. 处理响应
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        // a) 解析响应，反序列化
        bool ok = false;
        QString reason;
        auto pbResp = this->handleHttpResponse<bite_im::FriendAddProcessRsp>(httpResp, &ok, &reason);

        // b) 判定响应是否正确
        if (!ok) {
            LOG() << "[拒绝好友申请] 失败! requestId=" << pbReq.requestId() << ", reason=" << reason;
            return ;
        }

        // c) 在 DataCenter 中从好友申请列表中删除掉
        dataCenter->removeFromApplyList(userId);

        // d) 发送信号，通知调用者
        emit dataCenter->rejectFriendApplyDone();

        // e) 打印日志
        LOG() << "[拒绝好友申请] 响应完成 requestId=" << pbReq.requestId();
    });
}

void NetClient::createGroupChatSession(const QString &loginSessionId, const QList<QString> &userIdList)
{
    // 1. 通过 protobuf 构造 body 并序列化
    bite_im::ChatSessionCreateReq pbReq;
    pbReq.setRequestId(makeRequestId());
    pbReq.setSessionId(loginSessionId);
    pbReq.setChatSessionName("新的群聊");
    pbReq.setMemberIdList(userIdList);
    QByteArray body = pbReq.serialize(&serializer);
    LOG() << "[创建群聊会话] 发送请求 requestId=" << pbReq.requestId() << ", loginSessionId=" << loginSessionId << ", userIdList=" << userIdList;

    // 2. 发送 HTTP 请求
    QNetworkReply* httpResp = this->sendHttpRequest("/service/friend/create_chat_session", body);

    // 3. 处理响应
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        // a) 解析响应，反序列化
        bool ok = false;
        QString reason;
        auto pbResp = this->handleHttpResponse<bite_im::ChatSessionCreateRsp>(httpResp, &ok, &reason);

        // b) 判定响应是否正确
        if (!ok) {
            LOG() << "[创建群聊会话] 失败! requestId=" << pbReq.requestId() << ", reason=" << reason;
            return ;
        }

        // c) 在 DataCenter 中存储数据 由于此处创建好的会话，是 websocket 推送过来的
        // 这里无需更新 DataCenter

        // d) 发送信号，通知调用者
        emit dataCenter->createGroupChatSessionDone();

        // e) 打印日志
        LOG() << "[创建群聊会话] 响应完成 requestId=" << pbReq.requestId();
    });
}

void NetClient::getMemberList(const QString &loginSessionId, const QString &chatSessionId)
{
    // 1. 通过 protobuf 构造 body 并序列化
    bite_im::GetChatSessionMemberReq pbReq;
    pbReq.setSessionId(loginSessionId);
    pbReq.setChatSessionId(chatSessionId);
    pbReq.setRequestId(makeRequestId());
    QByteArray body = pbReq.serialize(&serializer);
    LOG() << "[获取会话成员列表] 发送请求 requestId=" << pbReq.requestId() << ", loginSessionId=" << loginSessionId << ", chatSeesionId=" << chatSessionId;

    // 2. 发送 HTTP 请求
    QNetworkReply* httpResp = this->sendHttpRequest("/service/friend/get_chat_session_member", body);

    // 3. 处理响应
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        // a) 解析响应，反序列化
        bool ok = false;
        QString reason;
        auto pbResp = this->handleHttpResponse<bite_im::GetChatSessionMemberRsp>(httpResp, &ok, &reason);

        // b) 判定响应是否正确
        if (!ok) {
            LOG() << "[获取会话成员列表] 失败! requestId=" << pbReq.requestId() << ", reason=" << reason;
            return ;
        }

        // c) 把结果记录到 DataCenter
        dataCenter->resetMembetList(chatSessionId, pbResp->memberInfoList());

        // d) 发送信号，通知调用者
        emit dataCenter->getMemberListDone(chatSessionId);

        // e) 打印日志
        LOG() << "[获取会话成员列表] 响应完成 requestId=" << pbReq.requestId();
    });
}

void NetClient::searchUser(const QString &loginSessionId, const QString &searchKey)
{
    // 1. 通过 protobuf 构造 body 并序列化
    bite_im::FriendSearchReq pbReq;
    pbReq.setRequestId(makeRequestId());
    pbReq.setSessionId(loginSessionId);
    pbReq.setSearchKey(searchKey);
    QByteArray body = pbReq.serialize(&serializer);
    LOG() << "[搜索用户] 发送请求 requestId=" << pbReq.requestId() << ", loginSessionId=" << loginSessionId << ", searchKey=" << searchKey;

    // 2. 发送 HTTP 请求
    QNetworkReply* httpResp = this->sendHttpRequest("/service/friend/search_friend", body);

    // 3. 处理响应
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        // a) 解析响应，反序列化
        bool ok = false;
        QString reason;
        auto pbResp = this->handleHttpResponse<bite_im::FriendSearchRsp>(httpResp, &ok, &reason);

        // b) 判定响应是否正确
        if (!ok) {
            LOG() << "[搜索用户] 失败! requestId=" << pbReq.requestId() << ", reason=" << reason;
            return ;
        }

        // c) 把结果记录到 DataCenter
        dataCenter->resetSearchUserResult(pbResp->userInfo());

        // d) 发送信号，通知调用者
        emit dataCenter->searchUserDone();

        // e) 打印日志
        LOG() << "[搜索用户] 响应完成 requestId=" << pbReq.requestId();
    });
}

void NetClient::searchMessage(const QString &loginSessionId, const QString &chatSessionId, const QString &searchKey)
{
    // 1. 通过 protobuf 构造 body 并序列化
    bite_im::MsgSearchReq pbReq;
    pbReq.setRequestId(makeRequestId());
    pbReq.setSessionId(loginSessionId);
    pbReq.setChatSessionId(chatSessionId);
    pbReq.setSearchKey(searchKey);
    QByteArray body = pbReq.serialize(&serializer);
    LOG() << "[按关键词搜索历史消息] 发送请求 requestId=" << pbReq.requestId() << ", loginSessionId=" << loginSessionId
          << ", chatSessionId=" << chatSessionId << ", searchKey=" << searchKey;


    // 2. 发送 HTTP 请求
    QNetworkReply* httpResp = this->sendHttpRequest("/service/message_storage/search_history", body);

    // 3. 处理响应
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        // a) 解析响应，反序列化
        bool ok = false;
        QString reason;
        auto pbResp = this->handleHttpResponse<bite_im::MsgSearchRsp>(httpResp, &ok, &reason);

        // b) 判定响应是否正确
        if (!ok) {
            LOG() << "[按关键词搜索历史消息] 失败! requestId=" << pbReq.requestId() << ", reason=" << reason;
            return ;
        }

        // c) 把结果记录到 DataCenter
        dataCenter->resetSearchMessageResult(pbResp->msgList());

        // d) 发送信号，通知调用者
        emit dataCenter->searchMessageDone();

        // e) 打印日志
        LOG() << "[按关键词搜索历史消息] 响应完成 requestId=" << pbReq.requestId();
    });
}

void NetClient::searchMessageByTime(const QString &loginSessionId, const QString &chatSessionId, const QDateTime &begTime, const QDateTime &endTime)
{
    // 1. 通过 protobuf 构造 body 并序列化
    bite_im::GetHistoryMsgReq pbReq;
    pbReq.setRequestId(makeRequestId());
    pbReq.setSessionId(loginSessionId);
    pbReq.setChatSessionId(chatSessionId);
    pbReq.setStartTime(begTime.toSecsSinceEpoch());
    pbReq.setOverTime(endTime.toSecsSinceEpoch());
    QByteArray body = pbReq.serialize(&serializer);
    LOG() << "[按时间搜索历史消息] 发送请求 requestId=" << pbReq.requestId() << ", loginSessionId=" << loginSessionId
          << ", chatSessionId=" << chatSessionId << ", begTime=" << begTime << ", endTime=" << endTime;


    // 2. 发送 HTTP 请求
    QNetworkReply* httpResp = this->sendHttpRequest("/service/message_storage/get_history", body);

    // 3. 处理响应
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        // a) 解析响应，反序列化
        bool ok = false;
        QString reason;
        auto pbResp = this->handleHttpResponse<bite_im::GetHistoryMsgRsp>(httpResp, &ok, &reason);

        // b) 判定响应是否正确
        if (!ok) {
            LOG() << "[按时间搜索历史消息] 失败! requestId=" << pbReq.requestId() << ", reason=" << reason;
            return ;
        }

        // c) 把结果记录到 DataCenter
        dataCenter->resetSearchMessageResult(pbResp->msgList());

        // d) 发送信号，通知调用者
        emit dataCenter->searchMessageDone();

        // e) 打印日志
        LOG() << "[按时间搜索历史消息] 响应完成 requestId=" << pbReq.requestId();
    });
}

void NetClient::userLogin(const QString &username, const QString &passwd)
{
    // 1. 通过 protobuf 构造 body 并序列化
    bite_im::UserLoginReq pbReq;
    pbReq.setRequestId(makeRequestId());
    pbReq.setNickname(username);
    pbReq.setPassword(passwd);
    pbReq.setVerifyCode("");
    pbReq.setVerifyCodeId("");
    QByteArray body = pbReq.serialize(&serializer);
    LOG() << "[用户名登录] 发送请求 requestId=" << pbReq.requestId() << ", username=" << username << ", passwd=" << passwd;

    // 2. 发送 HTTP 请求
    QNetworkReply* httpResp = this->sendHttpRequest("/service/user/username_login", body);

    // 3. 处理响应
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        // a) 解析响应，反序列化
        bool ok = false;
        QString reason;
        auto pbResp = this->handleHttpResponse<bite_im::UserLoginRsp>(httpResp, &ok, &reason);

        // b) 判定响应是否正确
        if (!ok) {
            LOG() << "[用户名登录] 失败! requestId=" << pbReq.requestId() << ", reason=" << reason;
            emit dataCenter->userLoginDone(false, reason);
            return ;
        }

        // c) 把结果记录到 DataCenter
        dataCenter->resetLoginSessionId(pbResp->loginSessionId());

        // d) 发送信号，通知调用者
        emit dataCenter->userLoginDone(true, "");

        // e) 打印日志
        LOG() << "[用户名登录] 响应完成 requestId=" << pbReq.requestId();
    });
}

void NetClient::userRegister(const QString &username, const QString &passwd)
{
    // 1. 通过 protobuf 构造 body 并序列化
    bite_im::UserRegisterReq pbReq;
    pbReq.setRequestId(makeRequestId());
    pbReq.setNickname(username);
    pbReq.setPassword(passwd);
    pbReq.setVerifyCode("");
    pbReq.setVerifyCodeId("");
    QByteArray body = pbReq.serialize(&serializer);
    LOG() << "[用户名注册] 发送请求 requestId=" << pbReq.requestId() << ", username=" << username << ", passwd=" << passwd;

    // 2. 发送 HTTP 请求
    QNetworkReply* httpResp = this->sendHttpRequest("/service/user/username_register", body);

    // 3. 处理响应
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        // a) 解析响应，反序列化
        bool ok = false;
        QString reason;
        auto pbResp = this->handleHttpResponse<bite_im::UserRegisterRsp>(httpResp, &ok, &reason);

        // b) 判定响应是否正确
        if (!ok) {
            LOG() << "[用户名注册] 失败! requestId=" << pbReq.requestId() << ", reason=" << reason;
            emit dataCenter->userRegisterDone(false, reason);
            return ;
        }

        // c) 对于注册来说不需要保存任何内容

        // d) 发送信号，通知调用者
        emit dataCenter->userRegisterDone(true, "");

        // e) 打印日志
        LOG() << "[用户名注册] 响应完成 requestId=" << pbReq.requestId();
    });
}

void NetClient::phoneLogin(const QString &phone, const QString &verifyCodeId, const QString &verifyCode)
{
    // 1. 通过 protobuf 构造 body 并序列化
    bite_im::PhoneLoginReq pbReq;
    pbReq.setRequestId(makeRequestId());
    pbReq.setPhoneNumber(phone);
    pbReq.setVerifyCodeId(verifyCodeId);
    pbReq.setVerifyCode(verifyCode);
    QByteArray body = pbReq.serialize(&serializer);
    LOG() << "[手机号登录] 发送请求 requestId=" << pbReq.requestId() << ", phone=" << phone << ", verifyCodeId=" << verifyCodeId << ", verifyCode=" << verifyCode;

    // 2. 发送 HTTP 请求
    QNetworkReply* httpResp = this->sendHttpRequest("/service/user/phone_login", body);

    // 3. 处理响应
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        // a) 解析响应，反序列化
        bool ok = false;
        QString reason;
        auto pbResp = this->handleHttpResponse<bite_im::PhoneLoginRsp>(httpResp, &ok, &reason);

        // b) 判定响应是否正确
        if (!ok) {
            LOG() << "[手机号登录] 失败! requestId=" << pbReq.requestId() << ", reason=" << reason;
            emit dataCenter->phoneLoginDone(false, reason);
            return ;
        }

        // c) 把结果记录到 DataCenter
        dataCenter->resetLoginSessionId(pbResp->loginSessionId());

        // d) 发送信号，通知调用者
        emit dataCenter->phoneLoginDone(true, "");

        // e) 打印日志
        LOG() << "[手机号登录] 响应完成 requestId=" << pbReq.requestId();
    });
}

void NetClient::phoneRegister(const QString &phone, const QString &verifyCodeId, const QString &verifyCode)
{
    // 1. 通过 protobuf 构造 body 并序列化
    bite_im::PhoneRegisterReq pbReq;
    pbReq.setRequestId(makeRequestId());
    pbReq.setPhoneNumber(phone);
    pbReq.setVerifyCode(verifyCode);
    pbReq.setVerifyCodeId(verifyCodeId);
    QByteArray body = pbReq.serialize(&serializer);
    LOG() << "[手机号注册] 发送请求 requestId=" << pbReq.requestId() << ", phone=" << phone << ", verifyCodeId=" << verifyCodeId << ", verifyCode=" << verifyCode;

    // 2. 发送 HTTP 请求
    QNetworkReply* httpResp = this->sendHttpRequest("/service/user/phone_register", body);

    // 3. 处理响应
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        // a) 解析响应，反序列化
        bool ok = false;
        QString reason;
        auto pbResp = this->handleHttpResponse<bite_im::PhoneRegisterRsp>(httpResp, &ok, &reason);

        // b) 判定响应是否正确
        if (!ok) {
            LOG() << "[手机号注册] 失败! requestId=" << pbReq.requestId() << ", reason=" << reason;
            emit dataCenter->phoneRegisterDone(false, reason);
            return ;
        }

        // c) 对于注册来说不需要保存任何内容

        // d) 发送信号，通知调用者
        emit dataCenter->phoneRegisterDone(true, "");

        // e) 打印日志
        LOG() << "[手机号注册] 响应完成 requestId=" << pbReq.requestId();
    });
}

void NetClient::getSingleFile(const QString &loginSessionId, const QString &fileId)
{
    // 1. 通过 protobuf 构造请求 body 并序列化
    bite_im::GetSingleFileReq pbReq;
    pbReq.setRequestId(makeRequestId());
    pbReq.setSessionId(loginSessionId);
    pbReq.setFileId(fileId);
    QByteArray body = pbReq.serialize(&serializer);
    LOG() << "[获取单个文件内容] 发送请求 requestId=" << pbReq.requestId() << ", loginSessionId=" << loginSessionId << ", fileId=" << fileId;

    // 2. 发送 HTTP 请求
    QNetworkReply* httpResp = this->sendHttpRequest("/service/file/get_single_file", body);

    // 3. 处理响应
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        // a) 解析响应，反序列化
        bool ok = false;
        QString reason;
        auto pbResp = this->handleHttpResponse<bite_im::GetSingleFileRsp>(httpResp, &ok, &reason);

        // b) 判定响应是否正确
        if (!ok) {
            LOG() << "[获取单个文件内容] 失败! requestId=" << pbReq.requestId() << ", reason=" << reason;
            return ;
        }

        // c) 保存响应结果，这里涉及到的文件可能会很多，不保存在 dataCenter 中了，直接通过信号把文件数据投送到调用者的位置上

        // d) 发送信号，通知调用者
        emit dataCenter->getSingleFileDone(fileId, pbResp->fileData().fileContent());

        // e) 打印日志
        LOG() << "[获取单个文件内容] 响应完成 requestId=" << pbReq.requestId();
    });
}

void NetClient::speechConvertText(const QString &loginSessionId, const QString &fileId, const QByteArray &content)
{
    // 1. 通过 protobuf 构造请求 body 并序列化
    bite_im::SpeechRecognitionReq pbReq;
    pbReq.setRequestId(makeRequestId());
    pbReq.setSessionId(loginSessionId);
    pbReq.setSpeechContent(content);
    QByteArray body = pbReq.serialize(&serializer);
    LOG() << "[语音转文字] 发送请求 requestId=" << pbReq.requestId() << ", loginSessionId=" << loginSessionId;

    // 2. 发送 HTTP 请求
    QNetworkReply* httpResp = this->sendHttpRequest("/service/speech/recognition", body);

    // 3. 处理响应
    connect(httpResp, &QNetworkReply::finished, this, [=]() {
        // a) 解析响应，反序列化
        bool ok = false;
        QString reason;
        auto pbResp = this->handleHttpResponse<bite_im::SpeechRecognitionRsp>(httpResp, &ok, &reason);

        // b) 判定响应是否正确
        if (!ok) {
            LOG() << "[语音转文字] 失败! requestId=" << pbReq.requestId() << ", reason=" << reason;
            return ;
        }

        // c) 保存响应结果

        // d) 发送信号，通知调用者
        emit dataCenter->speechConvertTextDone(fileId, pbResp->recognitionResult());

        // e) 打印日志
        LOG() << "[语音转文字] 响应完成 requestId=" << pbReq.requestId();
    });
}

} // end network
